<?php

class base_component_compiler {

    var $left_delimiter = "<{";

    var $right_delimiter = "}>";

    var $enable_strip_whitespace = false;

    var $_vars = array();

    // var $_plugins = array(); // stores all internal plugins
    var $_file = "";
 // the current file we are processing
    var $_literal = array();
 // stores all literal blocks
    var $_foreachelse_stack = array();

    var $_for_stack = 0;

    var $_if_stack = '';

    var $_sectionelse_stack = array();
 // keeps track of whether section had 'else' part
    var $_switch_stack = array();

    var $_tag_stack = array();

    var $_require_stack = array();
 // stores all files that are "required" inside of the template
    var $_block_stack = array();

    var $_head_stack = array();

    var $bundle_vars = array();

    var $compile_helper = array();

    var $view_helper = array();

    function __construct(&$controller) {
        $this->controller = $controller;
        
        foreach (kernel::servicelist('view_compile_helper') as $helper) {
            foreach (get_class_methods($helper) as $method) {
                if (substr($method, 0, 8) == 'compile_') {
                    $this->set_compile_helper($method, $helper);
                }
            }
        }
        
        foreach (kernel::servicelist('view_helper') as $helper_path => $helper) {
            foreach (get_class_methods($helper) as $method) {
                $this->set_view_helper($method, $helper_path);
            }
        }
    }

    function set_compile_helper($method, &$helper) {
        $this->compile_helper[$method] = $helper;
    }

    function set_view_helper($method, $helper_path) {
        $this->view_helper[$method] = $helper_path;
    }

    function compile_file($file_path) {
        if (file_exists($file_path)) {
            return $this->compile(file_get_contents($file_path));
        } else {
            trigger_error('compile file does\'s not exists [' . $file_path . ']', E_USER_ERROR);
            return false;
        }
    }

    function &compile($file_contents) {
        $this->_if_stack = '';
        $compiled_text = '';
        $this->_block_stack = array();
        
        foreach ((array) kernel::servicelist('view_compile_prefilter') as $object) {
            $file_contents = $object->process($func, $file_contents);
        }
        
        $ldq = preg_quote($this->left_delimiter, '!');
        $rdq = preg_quote($this->right_delimiter, '!');
        $file_contents = preg_replace("!{$ldq}\*.*?\*{$rdq}!su", '', $file_contents);
        $file_contents = preg_replace("!(\<\?|\?\>)!", '<?php echo \'\1\'; ?>', $file_contents);
        if ($this->bundle_vars) {
            $this->bundle_vars_re = '!\$(' . implode('|', $this->bundle_vars) . ')!';
        }
        
        foreach (preg_split('!' . $ldq . '(\s*(?:\/|)[a-z][a-z\_0-9]*|)(.*?)' . $rdq . '!isu', $file_contents, - 1, PREG_SPLIT_DELIM_CAPTURE) as $i => $v) {
            $i = $i % 3;
            if ($i == 0) {
                if (strpos($this->_if_stack, '2') === false) {
                    $compiled_text .= $v;
                }
            } elseif ($i == 1) {
                $func = trim($v);
            } else {
                
                $bundle_var_only = null;
                $argments = $this->_parse($v, $bundle_var_only);
                if ($bundle_var_only === null) {
                    $bundle_var_only = strpos(str_replace('$this->bundle_vars', '', $argments), '$') === false;
                }
                
                if ($func) {
                    $_result = $this->_parse_function($func, $argments, $bundle_var_only);
                    if (strpos($this->_if_stack, '2') === false) {
                        $compiled_text .= $_result;
                    }
                } elseif (strpos($this->_if_stack, '2') === false) {
                    ob_start();
                    $argments = $this->_fix_modifier($argments, true, $bundle_var_only);
                    $a = ob_get_contents();
                    ob_end_clean();
                    if ($bundle_var_only) {
                        if ($argments) {
                            eval('$out = ' . $argments . ';');
                        } else {
                            echo $a;
                        }
                        $compiled_text .= $out;
                    } else {
                        $compiled_text .= '<?php echo ' . $argments . '; ?>';
                    }
                }
            }
        }
        if ($this->_block_stack) {
            trigger_error("Block " . implode(',', $this->_block_stack) . " not closed", E_USER_ERROR);
        }
        $this->post_compile($compiled_text);
        return $compiled_text;
    }

    function post_compile(&$compiled_text) {
        foreach ((array) kernel::servicelist('view_compile_postfilter') as $object) {
            $file_contents = $object->process($func, $file_contents);
        }
        
        preg_match_all("/" . preg_quote('$this->__view_helper_model[\'') . "([^']*)" . preg_quote('\']->') . "/isu", $compiled_text, $matchs);
        if (count($matchs[1])) {
            $helpers = array_unique($matchs[1]);
            foreach ($helpers as $helper) {
                $compiled_text = '<?php $this->__view_helper_model[\'' . $helper . '\'] = kernel::single(\'' . $helper . '\'); ?>' . $compiled_text;
            }
        } // todo: include所需的view_helper_model
        
        if ($this->enable_strip_whitespace) {
            $compiled_text = $this->strip_whitespace($compiled_text);
        }
        
        $this->_require_stack = array();
        $this->_head_stack = array();
        $compiled_text = preg_replace(array(
            '/\<\?php\s*\?\>/',
            '/\?\>\s*\<\?php/'
        ), '', $compiled_text);
    }

    function _parse_function($function, $arguments, $bundle_var_only = false) {
        switch ($function) {
            // case 't':
            // case '/t':
            // return;
            
            case 'ldelim':
                return $this->left_delimiter;
            
            case 'rdelim':
                return $this->right_delimiter;
            
            case 'dump':
                $args = $this->_parse_arguments($arguments, false);
                return '<?php var_' . 'dump(' . $args['var'] . '); ?>';
            
            case 'link':
                $_args = $this->_parse_arguments($arguments);
                if (! isset($_args['app'])) {
                    $_args['app'] = "'" . $this->controller->app->app_id . "'";
                }
                foreach ($_args as $key => $value) {
                    if (is_bool($value)) {
                        $value = $value ? 'true' : 'false';
                    } elseif (is_null($value)) {
                        $value = 'null';
                    }
                    $_args[$key] = "'$key' => $value";
                }
                return '<?php echo kernel::router()->gen_url(array(' . implode(',', (array) $_args) . ')); ?>';
            
            case 'foreachelse':
                return "<?php }else{ ?>";
            
            case 'break':
            case 'continue':
                return '<?php ' . $function . '; ?>';
                break;
            
            case 'if':
                $arguments = $this->_arguments_if($arguments, $bundle_var_only);
                if ($bundle_var_only) {
                    eval('$arguments=(' . $arguments . ');');
                    $this->_if_stack = ($arguments ? '106' : '206') . $this->_if_stack;
                    return;
                } else {
                    $this->_if_stack = '000' . $this->_if_stack;
                    return '<?php if(' . $arguments . '){ ?>';
                }
            case 'else':
                if ($this->_if_stack{1} == '5') {
                    $this->_if_stack{0} = '2';
                    return;
                } elseif ($this->_if_stack{0} == '0') {
                    $this->_if_stack{1} = '5';
                    return '<?php }else{ ?>';
                } elseif ($this->_if_stack{0} == '1') {
                    $this->_if_stack{0} = '2';
                    $this->_if_stack{1} = '5';
                    return;
                } elseif ($this->_if_stack{0} == '2') {
                    $this->_if_stack{0} = '1';
                    return;
                }
            case 'elseif':
                if ($this->_if_stack{1} == '5') {
                    $this->_if_stack{0} = '2';
                    return;
                } elseif ($this->_if_stack{0} == '0') { // 之前存在if判断
                    $arguments = $this->_arguments_if($arguments, $bundle_var_only);
                    if ($bundle_var_only) {
                        eval('$arguments=(' . $arguments . ');');
                        if ($arguments) {
                            return $this->_parse_function('else', $arguments, $bundle_var_only);
                        } else {
                            $this->_if_stack{0} = '2';
                            $this->_if_stack{2} = '6';
                            return;
                        }
                    } else {
                        return '<?php }elseif(' . $arguments . '){ ?>';
                    }
                } elseif ($this->_if_stack{0} == '2') { // 之前为否
                    $this->_parse_function('/if', $arguments, $bundle_var_only);
                    return $this->_parse_function('if', $arguments, $bundle_var_only);
                } elseif ($this->_if_stack{0} == '1') { // 之前为绝对是， 禁掉今后所有
                    return $this->_parse_function('else', $arguments, $bundle_var_only);
                }
            case '/if':
                if ($this->_if_stack{2} == '6') {
                    $_result = '';
                } else {
                    $_result = '<?php } ?>';
                }
                $this->_if_stack = isset($this->_if_stack{5}) ? substr($this->_if_stack, 3) : '';
                return $_result;
            
            case 'foreach':
                $_args = $this->_parse_arguments($arguments, $bundle_var_only);
                if (! isset($_args['from'])) {
                    trigger_error("missing 'from' attribute in 'foreach' in " . __FILE__ . ' on line ' . __LINE__, E_USER_ERROR);
                }
                if (! isset($_args['value']) && ! isset($_args['item'])) {
                    trigger_error("missing 'value' attribute in 'foreach' in " . __FILE__ . ' on line ' . __LINE__, E_USER_ERROR);
                }
                if (isset($_args['value'])) {
                    $_args['value'] = $this->_dequote($_args['value']);
                } elseif (isset($_args['item'])) {
                    $_args['value'] = $this->_dequote($_args['item']);
                }
                isset($_args['key']) ? $_args['key'] = "\$this->_vars['" . $this->_dequote($_args['key']) . "'] => " : $_args['key'] = '';
                if ($_args['name']) {
                    array_push($this->_foreachelse_stack, $_args['name']);
                    $_result = '<?php $this->_env_vars[\'foreach\'][' . $_args['name'] . ']=array(\'total\'=>count(' . $_args['from'] . '),\'iteration\'=>0);foreach ((array)' . $_args['from'] . ' as ' . $_args['key'] . '$this->_vars[\'' . $_args['value'] . '\']){
                        $this->_env_vars[\'foreach\'][' . $_args['name'] . '][\'first\'] = ($this->_env_vars[\'foreach\'][' . $_args['name'] . '][\'iteration\']==0);
                        $this->_env_vars[\'foreach\'][' . $_args['name'] . '][\'iteration\']++;
                        $this->_env_vars[\'foreach\'][' . $_args['name'] . '][\'last\'] = ($this->_env_vars[\'foreach\'][' . $_args['name'] . '][\'iteration\']==$this->_env_vars[\'foreach\'][' . $_args['name'] . '][\'total\']);
?>';
                } else {
                    array_push($this->_foreachelse_stack, false);
                    $_result = '<?php if(' . $_args['from'] . ')foreach ((array)' . $_args['from'] . ' as ' . $_args['key'] . '$this->_vars[\'' . $_args['value'] . '\']){ ?>';
                }
                return $_result;
            
            case '/foreach':
                if ($name = array_pop($this->_foreachelse_stack)) {
                    return '<?php } unset($this->_env_vars[\'foreach\'][' . $name . ']); ?>';
                } else {
                    return '<?php } ?>';
                }
            
            case 'literal':
            case 'for':
            case '/for':
            case 'section':
            case 'sectionelse':
            case '/section':
            case 'while':
            case '/while':
                return;
            
            case 'switch':
                $_args = $this->_parse_arguments($arguments, $bundle_var_only);
                if (! isset($_args['from'])) {
                    trigger_error("missing 'from' attribute in 'switch' in " . __FILE__ . ' on line ' . __LINE__, E_USER_ERROR);
                }
                array_push($this->_switch_stack, array(
                    "matched" => false,
                    "var" => $this->_dequote($_args['from'])
                ));
                return;
            
            case '/switch':
                array_pop($this->_switch_stack);
                return '<?php break; endswitch; ?>';
            
            case 'case':
                if (count($this->_switch_stack) > 0) {
                    $_result = "<?php ";
                    $_args = $this->_parse_arguments($arguments, $bundle_var_only);
                    $_index = count($this->_switch_stack) - 1;
                    if (! $this->_switch_stack[$_index]["matched"]) {
                        $_result .= 'switch(' . $this->_switch_stack[$_index]["var"] . '): ';
                        $this->_switch_stack[$_index]["matched"] = true;
                    } else {
                        $_result .= 'break; ';
                    }
                    if (! empty($_args['value'])) {
                        $_result .= 'case ' . $_args['value'] . ': ';
                    } else {
                        $_result .= 'default: ';
                    }
                    return $_result . ' ?>';
                } else {
                    trigger_error("unexpected 'case', 'case' can only be in a 'switch' in " . __FILE__ . ' on line ' . __LINE__, E_USER_ERROR);
                }
                break;
            
            case 'assign':
                $_args = $this->_parse_arguments($arguments, $bundle_var_only);
                
                if (! isset($_args['var'])) {
                    trigger_error("missing 'var' attribute in 'pagedata' in " . __FILE__ . ' on line ' . __LINE__, E_USER_ERROR);
                }
                if (! isset($_args['value'])) {
                    trigger_error("missing 'value' attribute in 'pagedata' in " . __FILE__ . ' on line ' . __LINE__, E_USER_ERROR);
                }
                if (false === $_args['value']) {
                    $_args['value'] = 'false';
                } elseif (null === $_args['value']) {
                    $_args['value'] = 'null';
                } elseif ('' === $_args['value']) {
                    $_args['value'] = '';
                }
                return '<?php $this->_vars[' . $_args['var'] . ']=' . $_args['value'] . '; ?>';
            
            case 'include':
                return $this->tpl_compile_include($arguments, $this);
            
            default:
                $_result = "";
                if ($this->_compile_ui_function($function, $arguments, $_result)) {
                    return $_result;
                } elseif ($this->_compile_compiler_function($function, $arguments, $bundle_var_only, $_result)) {
                    return $_result;
                } elseif ($this->_compile_custom_block($function, $arguments, $_result)) {
                    if ($function{0} == '/') {
                        if (substr($function, 1) != array_pop($this->_block_stack)) {
                            trigger_error('template: block function ' . $function . 'not closed', E_USER_ERROR);
                        }
                    } else {
                        $this->_block_stack[] = $function;
                    }
                    return $_result;
                } elseif ($this->_compile_custom_function($function, $arguments, $_result)) {
                    return $_result;
                } else {
                    trigger_error($function . " function does not exist in " . __FILE__ . ' on line ' . __LINE__, E_USER_ERROR);
                }
        }
    }

    function tpl_compile_include($arguments, &$object) {
        $_args = $object->_parse_arguments($arguments);
        $app = "'" . $this->controller->app->app_id . "'";
        
        $arg_list = array();
        if (empty($_args['file'])) {
            $object->trigger_error("missing 'file' attribute in include tag in " . __FILE__ . ' on line ' . __LINE__, E_USER_ERROR);
        }
        
        $is_theme = 'false';
        
        foreach ($_args as $arg_name => $arg_value) {
            
            if (is_bool($arg_value)) {
                $arg_value = $arg_value ? 'true' : 'false';
            }
            
            if ($arg_name == 'file') {
                $include_file = $arg_value;
                continue;
            } elseif ($arg_name == 'app') {
                $app = $arg_value;
                continue;
            } elseif ($arg_name == 'assign') {
                $assign_var = $arg_value;
                continue;
            } elseif ($arg_name == 'is_theme') {
                
                $is_theme = $arg_value;
                continue;
            }
            
            $arg_list[] = "'$arg_name' => $arg_value";
        }
        
        if ('true' == $is_theme) {
            
            $include_file = "'" . app::get('site')->getConf('current_theme') . "/" . str_replace(array(
                '"',
                '\''
            ), '', $include_file) . "'";
        }
        
        if (isset($assign_var)) {
            $output = '<?php $_tpl_tpl_vars = $this->_vars;' . "\n\$this->_vars[" . $assign_var . "] = \$this->_fetch_compile_include(" . $app . ',' . $include_file . ", array(" . implode(',', (array) $arg_list) . ")," . $is_theme . ");\n" . "\$this->_vars = \$_tpl_tpl_vars;\n" . "unset(\$_tpl_tpl_vars);\n" . ' ?>';
        } else {
            $output = '<?php $_tpl_tpl_vars = $this->_vars;' . "\necho \$this->_fetch_compile_include(" . $app . ',' . $include_file . ", array(" . implode(',', (array) $arg_list) . ")," . $is_theme . ");\n" . "\$this->_vars = \$_tpl_tpl_vars;\n" . "unset(\$_tpl_tpl_vars);\n" . ' ?>';
        }
        
        // echo $output;
        
        return $output;
    }

    function _arguments_if($arguments, $bundle_var_only) {
        if (! $this->_if_replace) {
            $to_replace = array(
                'is\s+not\s+odd' => '%2==0',
                'is\s+odd' => '%2==1',
                'is\s+not\s+even' => '%2==1',
                'is\s+even' => '%2==0',
                'neq' => '!=',
                'eq' => '==',
                'ne' => '!=',
                'lt' => '<',
                'gt' => '>',
                'lte' => '<=',
                'le' => '<=',
                'ge' => '>=',
                'and' => '&&',
                'or' => '||',
                'not' => '!',
                'mod' => '%',
                'is' => '=='
            );
            foreach ($to_replace as $k => $v) {
                $this->_if_replace[0][] = '!(\s+)' . $k . '(\s+)!i';
                $this->_if_replace[1][] = '\1' . $v . '\2';
            }
        }
        
        $this->_begin_fix_quote($arguments);
        $arguments = str_replace('||', ' or ', $arguments);
        $arguments = preg_replace($this->_if_replace[0], $this->_if_replace[1], $arguments . ' ');
        $a = explode(' ', $arguments);
        foreach ($a as $i => $line) {
            $a[$i] = $this->_fix_modifier($line, false, $bundle_var_only);
        }
        $arguments = implode(' ', $a);
        $this->_end_fix_quote($arguments);
        
        return $arguments;
    }

    function _parse_arguments($arguments, $bundle_var_only = false) {
        preg_match_all('/([a-z0-9\_\-]+)=(\'|"|)(.*?(?:[^\\\\]|))\2\s/isu', $arguments . ' ', $matches, PREG_SET_ORDER);
        $ret = array();
        foreach ($matches as $match) {
            if ($match[2]) {
                $ret[$match[1]] = $match[2] . $match[3] . $match[2];
            } else {
                $ret[$match[1]] = $this->_fix_modifier($match[3], true, $bundle_var_only);
            }
        }
        return $ret;
    }

    function _prepare_fix_quote($match) {
        $this->_fix_quotes[$this->_fix_quotes_seq] = $match[0];
        return '_!ok' . ($this->_fix_quotes_seq++) . '!_';
    }

    function _restone_fix_quote($match) {
        return $this->_fix_quotes[$match[1]];
    }

    function _begin_fix_quote(&$variable) {
        $this->_fix_quotes_seq = 0;
        $this->_fix_quotes = array();
        $variable = preg_replace_callback('/([\'"]).*?(?:[^\\\\]|)\1/u', array(
            &$this,
            '_prepare_fix_quote'
        ), $variable);
    }

    function _end_fix_quote(&$variable) {
        if ($this->_fix_quotes) {
            $variable = preg_replace_callback('/_!ok([0-9]+)!_/u', array(
                &$this,
                '_restone_fix_quote'
            ), $variable);
        }
    }

    function getRuntimeFunc($function, $real = true) {
        if (isset($this->view_helper[$function])) {
            if ($real === true) {
                return 'kernel::single(\'' . $this->view_helper[$function] . '\')->' . $function;
            } else {
                return '$this->__view_helper_model[\'' . $this->view_helper[$function] . '\']->' . $function;
            }
        } else {
            return false;
        }
    }

    function _fix_modifier($variable, $fix_quote = true, &$bundle_var_only) {
        if (strpos($variable, '|')) {
            if ($fix_quote)
                $this->_begin_fix_quote($variable);
            $_mods = explode('|', $variable);
            $variable = array_shift($_mods);
            foreach ($_mods as $mod) {
                if ($p = strpos($mod, ':')) {
                    $_arg = $variable . str_replace(':', ',', substr($mod, $p));
                    $mod = substr($mod, 0, $p);
                } else {
                    $_arg = $variable;
                }
                if ($mod{0} == '@') {
                    $mod = substr($mod, 1);
                }
                if (isset($this->compile_helper['compile_modifier_' . $mod])) {
                    $variable = $this->compile_helper['compile_modifier_' . $mod]->{'compile_modifier_' . $mod}($_arg, $this, $bundle_var_only);
                } elseif ($func = $this->getRuntimeFunc('modifier_' . $mod)) {
                    $variable = $func . '(' . $_arg . ')';
                } elseif (function_exists($mod)) {
                    $variable = $mod . '(' . $_arg . ')';
                } else {
                    $variable = "trigger_error(\"'" . $mod . "' modifier does not exist\", E_USER_NOTICE);";
                }
            }
            if ($fix_quote)
                $this->_end_fix_quote($variable);
        }
        
        return $variable;
    }

    function _parse($cmd_line, &$bundle_var_only) {
        $this->i = 0;
        $this->lib = array();
        if (preg_match('/(eval|exec|system|shell_exec|passthru|popen)(\s|\/\*(.*)\*\/)*\(.*\)/i', $cmd_line)) {
            return 0;
        }
        $res = preg_replace_callback('!(\$[a-z0-9\_\.\$\[\]\"\\\']+)!isu', array(
            &$this,
            '_in_str'
        ), $cmd_line);
        foreach ($this->lib as $i => $var) {
            $var_ns = '';
            if ($p = strpos($var, '.')) {
                $first = substr($var, 0, $p);
                
                if ($first == '$smarty') {
                    $first = '$env';
                    $var = '$env' . substr($var, 7);
                    $p = 4;
                }
                
                if ($first == '$env') {
                    $bundle_var_only = false;
                    
                    $p = strpos($var, '.', $p + 1);
                    if (! $p) {
                        $p = strlen($var);
                    }
                    $second = strtoupper(substr($var, 5, $p - 5));
                    
                    switch ($second) {
                        case 'CONF':
                            if ($p2 = strpos($var, '.', $p + 1)) {
                                $sub = substr($var, $p + 1, $p2 - $p - 1);
                                $var = 'app::get(\'' . $sub . '\')->getConf(\'' . substr($var, $p2 + 1) . '\')';
                            } else {
                                $var = '$this->app->getConf(\'' . substr($var, $p + 1) . '\')';
                            }
                            
                            $var_ns = - 1;
                            break;
                        case 'GET':
                        case 'POST':
                        case 'COOKIE':
                        case 'ENV':
                        case 'SERVER':
                        case 'SESSION':
                            if ($p) {
                                $var = substr($var, $p + 1);
                            } else {
                                $var = '';
                            }
                            $var_ns = '$_' . $second;
                            break;
                        case 'BASE_URL':
                            $var = '';
                            $var_ns = 'kernel::base_url()';
                            break;
                        case 'BASE_FULL_URL':
                            $var = '';
                            $var_ns = 'kernel::base_url(1)';
                            break;
                        case 'STATICS_HOST_URL':
                            $var = '';
                            $var_ns = 'kernel::get_app_statics_host_url()';
                            break;
                        case 'NOW':
                            $var = '';
                            $var_ns = 'time()';
                            break;
                        case 'SECTION':
                            $var = substr($var, $p + 1);
                            $var_ns = '$this->_sections';
                            break;
                        case 'LDELIM':
                            $var = '';
                            $var_ns = '$this->left_delimiter';
                            break;
                        case 'RDELIM':
                            $var = '';
                            $var_ns = '$this->right_delimiter';
                            break;
                        case 'TEMPLATE':
                            $var = '';
                            $var_ns = '$this->_file';
                            break;
                        case 'CONST':
                            $var_ns = 'constant(\'' . substr($var, $p + 1) . '\')';
                            $var = '';
                            break;
                        case 'APP':
                            if (isset($var{$p + 1})) {
                                if ($p2 = strpos($var, '.', $p + 1)) {
                                    $sub = substr($var, $p + 1, $p2 - $p - 1);
                                    if (strtoupper($sub) == 'CONF') {
                                        $var = '$this->app->getConf(\'' . substr($var, $p2 + 1) . '\')';
                                        $var_ns = - 1;
                                    } else {
                                        $var_ns = '$this->app->' . $sub;
                                        $var = substr($var, $p2);
                                    }
                                } else {
                                    $var_ns = '$this->app->' . substr($var, $p + 1);
                                    $var = '';
                                }
                            } else {
                                $var_ns = '\'App-\'.$this->app->ident';
                                $var = '';
                            }
                            break;
                        case 'FOREACH':
                            $var = substr($var, $p + 1);
                            $var_ns = '$this->_env_vars[\'foreach\']';
                            break;
                        case 'PC_URL':
                            $var = '';
                            $var_ns = 'app::get("site")->router()->gen_url(array("app"=>"site", "ctl"=>"default","act"=>"index"))';
                            break;
                        
                        default:
                            $var_ns = '$this->_env_vars[\'' . substr($var, 5, $p - 5) . '\']';
                            $var = substr($var, $p + 1);
                            break;
                    }
                } else {
                    $first = substr($first, 1);
                    if (isset($this->bundle_vars[$first])) {
                        $var_ns = '$this->bundle_vars[\'' . $first . '\']';
                    } else {
                        $var_ns = '$this->_vars[\'' . $first . '\']';
                    }
                    $var = substr($var, $p + 1);
                }
                if ($var_ns != - 1) {
                    $a = preg_split('/(\.|\[[\\\'a-z0-9\_\"]+\])/', $var, - 1, PREG_SPLIT_DELIM_CAPTURE);
                    if ($a) {
                        $var = '';
                        foreach ($a as $k => $l) {
                            if ($k % 2 == 1) {
                                if ($l != '.') {
                                    $var .= $l;
                                }
                            } else {
                                if (isset($l{0})) {
                                    if ($l{0} != '$' && $l{0} != '"' && $l{0} != '\'') {
                                        $l = "'" . $l . "'";
                                    }
                                    $var .= '[' . $l . ']';
                                }
                            }
                        }
                    }
                }
            }
            
            if ($var_ns != - 1) {
                if ($this->bundle_vars) {
                    $var = $var_ns . preg_replace(array(
                        $this->bundle_vars_re,
                        '!\$([a-z0-9\_]+)!iu'
                    ), array(
                        '$this->bundle_vars[\'\1\']',
                        '$this->_vars[\'\1\']'
                    ), $var);
                } else {
                    $var = $var_ns . preg_replace('!\$([a-z0-9\_]+)!iu', '$this->_vars[\'\1\']', $var);
                }
            }
            
            $var = preg_replace_callback('!"_s([0-9]+)s_"!', array(
                &$this,
                '_rest'
            ), $var);
            $this->lib[$i] = $var;
        }
        $res = preg_replace_callback('!"_s([0-9]+)s_"!', array(
            &$this,
            '_rest_root'
        ), $res);
        $this->lib = array();
        return $res;
    }

    function _rest_root($match) {
        return $this->lib[$match[1]];
    }

    function _rest($match) {
        return $this->lib[$match[1]];
    }

    function _in_str($varstr) {
        $varstr = $varstr[1];
        $varstr = preg_replace_callback('/\[(\$[a-z0-9\_\.]+)\]/iu', array(
            &$this,
            '_in_sub_str'
        ), $varstr);
        $this->lib[$this->i] = $varstr;
        return '"_s' . ($this->i++) . 's_"';
    }

    function _in_sub_str($varstr) {
        $varstr = $varstr[1];
        $varstr = preg_replace_callback('/\[(\$[a-z0-9\_\.]+)\]/iu', array(
            &$this,
            __METHOD__
        ), $varstr);
        $this->lib[$this->i] = $varstr;
        return '."_s' . ($this->i++) . 's_"';
    }

    function strip_whitespace($tpl_source) {
        $a = preg_split('/(<\s*(?:pre|script|textarea).*?>.*?<\s*\/\s*(?:pre|script|textarea)\s*>)/isu', $tpl_source, - 1, PREG_SPLIT_DELIM_CAPTURE);
        $token = 'o-o-o-o';
        $r = '';
        $tpl_source = '';
        foreach ($a as $k => $v) {
            if ($k % 2 == 0) {
                $r .= $v . $token;
                unset($a[$k]);
            }
        }
        $r = preg_replace('/\s+/s', ' ', $r);
        foreach (explode($token, $r) as $i => $txt) {
            $tpl_source .= $txt;
            if (isset($a[2 * $i + 1])) {
                $tpl_source .= $a[2 * $i + 1];
            }
        }
        return $tpl_source;
    }

    function _dequote($string) {
        if (($string{0} == "'" || $string{0} == '"') && $string{strlen($string) - 1} == $string{0}) {
            return substr($string, 1, - 1);
        } else {
            return $string;
        }
    }

    function _compile_ui_function($function, $arguments, &$_result) {
        if (method_exists($this->controller->ui(), $function)) {
            $_args = $this->_parse_arguments($arguments);
            foreach ($_args as $key => $value) {
                if (is_bool($value)) {
                    $value = $value ? 'true' : 'false';
                } elseif (is_null($value)) {
                    $value = 'null';
                }
                $_args[$key] = "'$key' => $value";
            }
            $_result = '<?php echo ';
            $_result .= '$this->ui()->' . $function . '(array(' . implode(',', (array) $_args) . '));';
            $_result .= '?>';
            return true;
        } else {
            return false;
        }
    }

    function _compile_compiler_function($function, $arguments, $bundle_var_only, &$_result) {
        $function = "compile_" . $function;
        if (method_exists($this->controller, $function)) {
            $_args = $this->_parse_arguments($arguments, $bundle_var_only);
            $_result = $this->controller->$function($_args, $this, $bundle_var_only);
            return true;
        } elseif (isset($this->compile_helper[$function])) {
            $object = $this->compile_helper[$function];
            $_args = $this->_parse_arguments($arguments, $bundle_var_only);
            $_result = '<?php ' . $object->$function($_args, $this, $bundle_var_only) . ' ?>';
            return true;
        } else {
            return false;
        }
    }

    function _compile_custom_function($function, $arguments, &$_result) {
        if (method_exists($this->controller, 'tpl_function_' . $function)) {
            $function = '$this->tpl_function_' . $function;
        } else {
            $function = $this->getRuntimeFunc("function_" . $function, false);
        }
        
        if ($function) {
            $_args = $this->_parse_arguments($arguments);
            foreach ($_args as $key => $value) {
                if (is_bool($value)) {
                    $value = $value ? 'true' : 'false';
                } elseif (is_null($value)) {
                    $value = 'null';
                }
                $_args[$key] = "'$key' => $value";
            }
            $_result = '<?php echo ';
            $_result .= $function . '(array(' . implode(',', (array) $_args) . '), $this);';
            $_result .= '?>';
            return true;
        } else {
            return false;
        }
    }

    function _compile_custom_block($function, $arguments, &$_result) {
        if ($function{0} == '/') {
            $start_tag = false;
            $function = substr($function, 1);
        } else {
            $start_tag = true;
        }
        if ($function_call = $this->getRuntimeFunc("block_" . $function, false)) {
            if ($start_tag) {
                $_args = $this->_parse_arguments($arguments);
                foreach ($_args as $key => $value) {
                    if (is_bool($value)) {
                        $value = $value ? 'true' : 'false';
                    } elseif (is_null($value)) {
                        $value = 'null';
                    }
                    $_args[$key] = "'$key' => $value";
                }
                $_result = "<?php \$this->_tag_stack[] = array('" . str_replace("'", "\\'", $function) . "', array(" . implode(',', (array) $_args) . ")); ";
                $_result .= $function_call . '(array(' . implode(',', (array) $_args) . '), null, $this); ';
                $_result .= 'ob_start(); ?>';
            } else {
                $_result .= '<?php $_block_content = ob_get_contents(); ob_end_clean(); ';
                $_result .= '$_block_content = ' . $function_call . '($this->_tag_stack[count($this->_tag_stack) - 1][1], $_block_content, $this); ';
                $_result .= 'echo $_block_content; array_pop($this->_tag_stack); $_block_content=\'\'; ?>';
            }
            return true;
        } else {
            return false;
        }
    }
}
